﻿using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Loong.Threading.Locks.Database
{
    public class DatabaseLockFactory : LockFactoryBase
    {
        private readonly LockOptions _lockOptions;
        private readonly IResourceLockDbAccessor _dbAccessor;

        public DatabaseLockFactory(
            ILogger<DatabaseLockFactory> logger,
            IOptionsMonitor<LockOptions> options,
            IResourceLockDbAccessor dbAccessor)
            : base(logger, options)
        {
            _lockOptions = options.CurrentValue;
            _dbAccessor = dbAccessor;
        }

        protected override async Task AcquireAsync(string resource, string owner, TimeSpan expiryTime)
        {
            var timer = Stopwatch.StartNew();

            do
            {
                try
                {
                    var resourceLock = await _dbAccessor.GetByResourceAsync(resource);
                    if (resourceLock == null)
                    {
                        resourceLock = new ResourceLock();
                        resourceLock.Resource = resource;
                        resourceLock.ExpireTime = DateTime.Now.Add(expiryTime);
                        resourceLock.LockOwner = owner;

                        await _dbAccessor.InsertAsync(resourceLock);

                        return;
                    }

                    if (resourceLock.ExpireTime < DateTime.Now)
                    {
                        resourceLock.ExpireTime = DateTime.Now.Add(expiryTime);
                        resourceLock.LockOwner = owner;

                        if (await _dbAccessor.UpdateLockAsync(resourceLock))
                        {
                            return;
                        }
                    }
                }
                catch { }

                await Task.Delay(_lockOptions.DefaultRetryTime);
            }
            while (timer.Elapsed < expiryTime);

            throw new LockTimeoutException(resource);
        }

        protected override void Release(string resource, string owner)
        {
            if (!_dbAccessor.ReleaseLock(resource, owner))
            {
                throw new LockReleaseException(resource);
            }
        }
    }
}
